﻿using System;

namespace Aeon.Emulator
{
    /// <summary>
    /// Provides methods for accessing the virtual machine's stack.
    /// </summary>
    internal abstract class SystemStack
    {
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the SystemStack class.
        /// </summary>
        /// <param name="vm">Current VirtualMachine instance.</param>
        protected SystemStack(VirtualMachine vm)
        {
            if(vm == null)
                throw new ArgumentNullException("vm");

            this.processor = vm.Processor;
            this.memory = vm.PhysicalMemory;
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Pushes a 16-bit integer onto the stack, advancing the stack pointer by two bytes.
        /// </summary>
        /// <param name="value">Value to push.</param>
        public abstract void PushUInt16(ushort value);
        /// <summary>
        /// Pushes a 32-bit integer onto the stack, advancing the stack pointer by four bytes.
        /// </summary>
        /// <param name="value">Value to push.</param>
        public abstract void PushUInt32(uint value);
        /// <summary>
        /// Pops a 16-bit integer from the stack, advancing the stack pointer by two bytes.
        /// </summary>
        /// <returns>Value popped from the stack.</returns>
        public abstract ushort PopUInt16();
        /// <summary>
        /// Pops a 32-bit integer from the stack, advancing the stack pointer by four bytes.
        /// </summary>
        /// <returns>Value popped from the stack.</returns>
        public abstract uint PopUInt32();
        /// <summary>
        /// Adds a value to the stack pointer.
        /// </summary>
        /// <param name="value">Value to add to the stack pointer.</param>
        public abstract void Add(uint value);
        /// <summary>
        /// Returns the top two bytes of the stack as a 16-bit integer.
        /// </summary>
        /// <returns>Top 16-bit value on the stack.</returns>
        public abstract ushort PeekUInt16();
        /// <summary>
        /// Returns the top four bytes of the stack as a 32-bit integer.
        /// </summary>
        /// <returns>Top 32-bit value on the stack.</returns>
        public abstract uint PeekUInt32();
        /// <summary>
        /// Returns the top six bytes of the stack as a 64-bit integer.
        /// </summary>
        /// <returns>Top 48-bit value on the stack.</returns>
        public abstract ulong PeekUInt48();
        #endregion

        #region Public Static Methods
        /// <summary>
        /// Returns a new SystemStack instance that uses 16-bit stack pointers.
        /// </summary>
        /// <param name="vm">VirtualMachine instance to associate with the stack.</param>
        /// <returns>New SystemStack instance that uses 16-bit stack pointers.</returns>
        public static SystemStack Get16BitStack(VirtualMachine vm)
        {
            if(vm == null)
                throw new ArgumentNullException("vm");

            return new SystemStack16(vm);
        }
        /// <summary>
        /// Returns a new SystemStack instance that uses 32-bit stack pointers.
        /// </summary>
        /// <param name="vm">VirtualMachine instance to associate with the stack.</param>
        /// <returns>New SystemStack instance that uses 32-bit stack pointers.</returns>
        public static SystemStack Get32BitStack(VirtualMachine vm)
        {
            if(vm == null)
                throw new ArgumentNullException("vm");

            return new SystemStack32(vm);
        }
        #endregion

        #region Protected Fields
        /// <summary>
        /// Current processor instance.
        /// </summary>
        protected readonly Processor processor;
        /// <summary>
        /// Current physical memory instance.
        /// </summary>
        protected readonly PhysicalMemory memory;
        #endregion

        #region Private SystemStack16 Class
        /// <summary>
        /// Provides stack manipulation with 16-bit pointers.
        /// </summary>
        private sealed class SystemStack16 : SystemStack
        {
            #region Constructors
            /// <summary>
            /// Initializes a new instance of the SystemStack16 class.
            /// </summary>
            /// <param name="vm">Current VirtualMachine instance.</param>
            public SystemStack16(VirtualMachine vm)
                : base(vm)
            {
            }
            #endregion

            #region Public Methods
            /// <summary>
            /// Pushes a 16-bit integer onto the stack, advancing the stack pointer by two bytes.
            /// </summary>
            /// <param name="value">Value to push.</param>
            public override void PushUInt16(ushort value)
            {
                this.processor.SP -= 2;
                unsafe
                {
                    uint address = this.processor.segmentBases[(int)SegmentIndex.SS] + this.processor.SP;
                    this.memory.SetUInt16(address, value);
                }
            }
            /// <summary>
            /// Pushes a 32-bit integer onto the stack, advancing the stack pointer by four bytes.
            /// </summary>
            /// <param name="value">Value to push.</param>
            public override void PushUInt32(uint value)
            {
                this.processor.SP -= 4;
                unsafe
                {
                    uint address = this.processor.segmentBases[(int)SegmentIndex.SS] + this.processor.SP;
                    this.memory.SetUInt32(address, value);
                }
            }
            /// <summary>
            /// Pops a 16-bit integer from the stack, advancing the stack pointer by two bytes.
            /// </summary>
            /// <returns>Value popped from the stack.</returns>
            public override ushort PopUInt16()
            {
                ushort value;
                unsafe
                {
                    uint address = this.processor.segmentBases[(int)SegmentIndex.SS] + this.processor.SP;
                    value = this.memory.GetUInt16(address);
                }
                this.processor.SP += 2;
                return value;
            }
            /// <summary>
            /// Pops a 32-bit integer from the stack, advancing the stack pointer by four bytes.
            /// </summary>
            /// <returns>Value popped from the stack.</returns>
            public override uint PopUInt32()
            {
                uint value;
                unsafe
                {
                    uint address = this.processor.segmentBases[(int)SegmentIndex.SS] + this.processor.SP;
                    value = this.memory.GetUInt32(address);
                }
                this.processor.SP += 4;
                return value;
            }
            /// <summary>
            /// Adds a value to the stack pointer.
            /// </summary>
            /// <param name="value">Value to add to the stack pointer.</param>
            public override void Add(uint value)
            {
                this.processor.SP += (ushort)value;
            }
            /// <summary>
            /// Returns the top two bytes of the stack as a 16-bit integer.
            /// </summary>
            /// <returns>Top 16-bit value on the stack.</returns>
            public override ushort PeekUInt16()
            {
                unsafe
                {
                    uint address = this.processor.segmentBases[(int)SegmentIndex.SS] + this.processor.SP;
                    return this.memory.GetUInt16(address);
                }
            }
            /// <summary>
            /// Returns the top four bytes of the stack as a 32-bit integer.
            /// </summary>
            /// <returns>Top 32-bit value on the stack.</returns>
            public override uint PeekUInt32()
            {
                unsafe
                {
                    uint address = this.processor.segmentBases[(int)SegmentIndex.SS] + this.processor.SP;
                    return this.memory.GetUInt32(address);
                }
            }
            /// <summary>
            /// Returns the top six bytes of the stack as a 64-bit integer.
            /// </summary>
            /// <returns>Top 48-bit value on the stack.</returns>
            public override ulong PeekUInt48()
            {
                unsafe
                {
                    uint address = this.processor.segmentBases[(int)SegmentIndex.SS] + this.processor.SP;
                    return this.memory.GetUInt64(address);
                }
            }
            #endregion
        }
        #endregion

        #region Private SystemStack32 Class
        /// <summary>
        /// Provides stack manipulation with 32-bit pointers.
        /// </summary>
        private sealed class SystemStack32 : SystemStack
        {
            #region Constructors
            /// <summary>
            /// Initializes a new instance of the SystemStack32 class.
            /// </summary>
            /// <param name="vm">Current VirtualMachine instance.</param>
            public SystemStack32(VirtualMachine vm)
                : base(vm)
            {
            }
            #endregion

            #region Public Methods
            /// <summary>
            /// Pushes a 16-bit integer onto the stack, advancing the stack pointer by two bytes.
            /// </summary>
            /// <param name="value">Value to push.</param>
            public override void PushUInt16(ushort value)
            {
                this.processor.ESP -= 2;
                unsafe
                {
                    uint address = this.processor.segmentBases[(int)SegmentIndex.SS] + this.processor.ESP;
                    this.memory.SetUInt16(address, value);
                }
            }
            /// <summary>
            /// Pushes a 32-bit integer onto the stack, advancing the stack pointer by four bytes.
            /// </summary>
            /// <param name="value">Value to push.</param>
            public override void PushUInt32(uint value)
            {
                this.processor.ESP -= 4;
                unsafe
                {
                    uint address = this.processor.segmentBases[(int)SegmentIndex.SS] + this.processor.ESP;
                    this.memory.SetUInt32(address, value);
                }
            }
            /// <summary>
            /// Pops a 16-bit integer from the stack, advancing the stack pointer by two bytes.
            /// </summary>
            /// <returns>Value popped from the stack.</returns>
            public override ushort PopUInt16()
            {
                ushort value;
                unsafe
                {
                    uint address = this.processor.segmentBases[(int)SegmentIndex.SS] + this.processor.ESP;
                    value = this.memory.GetUInt16(address);
                }
                this.processor.ESP += 2;
                return value;
            }
            /// <summary>
            /// Pops a 32-bit integer from the stack, advancing the stack pointer by four bytes.
            /// </summary>
            /// <returns>Value popped from the stack.</returns>
            public override uint PopUInt32()
            {
                uint value;
                unsafe
                {
                    uint address = this.processor.segmentBases[(int)SegmentIndex.SS] + this.processor.ESP;
                    value = this.memory.GetUInt32(address);
                }
                this.processor.ESP += 4;
                return value;
            }
            /// <summary>
            /// Adds a value to the stack pointer.
            /// </summary>
            /// <param name="value">Value to add to the stack pointer.</param>
            public override void Add(uint value)
            {
                this.processor.ESP += value;
            }
            /// <summary>
            /// Returns the top two bytes of the stack as a 16-bit integer.
            /// </summary>
            /// <returns>Top 16-bit value on the stack.</returns>
            public override ushort PeekUInt16()
            {
                unsafe
                {
                    uint address = this.processor.segmentBases[(int)SegmentIndex.SS] + this.processor.ESP;
                    return this.memory.GetUInt16(address);
                }
            }
            /// <summary>
            /// Returns the top four bytes of the stack as a 32-bit integer.
            /// </summary>
            /// <returns>Top 32-bit value on the stack.</returns>
            public override uint PeekUInt32()
            {
                unsafe
                {
                    uint address = this.processor.segmentBases[(int)SegmentIndex.SS] + this.processor.ESP;
                    return this.memory.GetUInt32(address);
                }
            }
            /// <summary>
            /// Returns the top six bytes of the stack as a 64-bit integer.
            /// </summary>
            /// <returns>Top 48-bit value on the stack.</returns>
            public override ulong PeekUInt48()
            {
                unsafe
                {
                    uint address = this.processor.segmentBases[(int)SegmentIndex.SS] + this.processor.ESP;
                    return this.memory.GetUInt64(address);
                }
            }
            #endregion
        }
        #endregion
    }
}
